package com.rc.saas.tenant.shiro;

import com.rc.saas.tenant.common.constant.ShiroConstants;
import com.rc.saas.tenant.common.constant.WebConstant;
import com.rc.saas.tenant.common.utils.BeanUtil;
import com.rc.saas.tenant.model.tenant.*;
import com.rc.saas.tenant.service.tenant.TenantMenuService;
import com.rc.saas.tenant.service.operator.TenantOperatorMenuService;
import com.rc.saas.tenant.service.tenant.TenantRoleService;
import com.rc.saas.tenant.service.tenant.TenantUserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Shiro 认证、授权
 * Created by sven on 2017/2/18.
 */
public class CustomRealm extends AuthorizingRealm {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    //1.Realm  注入的Service  必须加@Lazy，否则该service事务失效
    //2.不加@Lazy，可以直接注入mapper
    @Autowired
    @Lazy
    private TenantUserService tenantUserService;

    @Autowired
    @Lazy
    private TenantMenuService tenantMenuService;

    @Autowired
    @Lazy
    private TenantOperatorMenuService tenantOperatorMenuService;

    @Autowired
    private TenantRoleService tenantRoleService;

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 认证回调函数,登录验证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        //UsernamePasswordToken对象用来存放提交的登录信息
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        logger.info("验证当前Subject时获取到token为:{}", ReflectionToStringBuilder.toString(token, ToStringStyle.JSON_STYLE));

        TenantUserExample example = new TenantUserExample();
        example.createCriteria().andLoginNameEqualTo(token.getUsername());
        TenantUser user = tenantUserService.findUserByExample(example);

        //TenantUserExample example = new TenantUserExample();
        //example.createCriteria().andLoginNameEqualTo(token.getUsername());
        //TenantUser user = tenantUserService.selectByLoginName(token.getUsername());

        // 若存在，将此用户存放到登录认证info中，无需自己做密码对比，Shiro会为我们进行密码对比校验
        if (user == null) {
            throw new UnknownAccountException("帐号找不到");
        } else {
            //组装SimpleAuthenticationInfo交给AuthenticatingRealm使用CredentialsMatcher(doCredentialsMatch)进行密码匹配，如果觉得人家的不好可以自定义实现
            //SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
            //        user.getLoginName(), //用户名
            //        user.getPassword(), //密码(加密后)
            //        this.getName()  //realmName：当前realm对象的name，调用父类的getName()方法即可
            //);

            //SHIRO-REDIS插件必需这样写法
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    user, //用户信息
                    user.getPassword(), //密码(加密后)
                    this.getName()  //realmName：当前realm对象的name，调用父类的getName()方法即可
            );

            //保存到会话中
            //ShiroUtils.saveSession(Constants.SESSION_TENANT_USER_KEY, manager);

            //如果身份认证验证成功，返回一个AuthenticationInfo实现；
            return authenticationInfo;
        }
    }

    /**
     * 授权信息(shiro:hasPermission标签)才触发调用
     * 每次在访问设置了权限的页面时，都会去执行doGetAuthorizationInfo方法来判断当前用户是否具备访问权限
     * 如果访问的url只是设置了authc,是否不会执行此方法？
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
        //        + request.getServletPath();
        logger.info("doGetAuthorizationInfo授权:{}", ReflectionToStringBuilder.toString(principals, ToStringStyle.MULTI_LINE_STYLE));

        // 因为非正常退出，即没有显式调用 SecurityUtils.getSubject().logout()
        // (可能是关闭浏览器，或超时)，但此时缓存依旧存在(principals)，所以会自己跑到授权方法里。
        if (!SecurityUtils.getSubject().isAuthenticated()) {
            doClearCache(principals);
            SecurityUtils.getSubject().logout();
            return null;
        }

        TenantUser shiroUser = (TenantUser) principals.getPrimaryPrincipal();
        if (shiroUser != null) {
            Long roleId = BeanUtil.isValid(shiroUser.getTenantRoleId()) ? shiroUser.getTenantRoleId() : 0;
            AuthorizationInfo authorizationInfo = (AuthorizationInfo) redisTemplate.opsForValue()
                    .get("shiro:" + shiroUser.getTenantCode() + ":" + roleId);
            if (authorizationInfo != null) {
                return authorizationInfo;
            }
        }

        logger.info("doGetAuthorizationInfo授权:{}", shiroUser.getLoginName());
        if (shiroUser.getLoginName() != null) {
            //查找所有菜单
            //TenantUserExample userExample = new TenantUserExample();
            //userExample.createCriteria().andLoginNameEqualTo(shiroUser.getLoginName());
            TenantUser tenantUser = tenantUserService.selectByLoginName(shiroUser.getLoginName());

            //获取的SimpleAuthorizationInfo授权信息将会保存 缓存中
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

            Set<String> roleName = new HashSet<String>();
            Set<String> permissionNames = new HashSet<String>();

            //用户类型（0 运营商 1 分销商 2企业用户）默认0
            if (tenantUser.getUserType().equals(WebConstant.STATUS_ZERO)) {//运营商
                //if (StringUtils.isBlank(user.getTenantOperatorCode())) {//运营商
                //超级管理员拥有所有角色权限,但如果某一功能还没有授权给任何角色，那管理员也没有权限
                //TenantRoleExample roleExample = new TenantRoleExample();
                //roles = tenantOperatorMenuService.selectByExample(roleExample);

                //管理员拥有所有角色
                //for (TenantRole role : roles) {
                //    roleName.add(role.getRoleName());
                //}

                roleName.add(ShiroConstants.TENANT_ROLE_NAME);

                //管理员拥有所有permissions
                TenantMenuExample menuExample = new TenantMenuExample();
                List<TenantMenu> tenantMenus = tenantMenuService.selectByExample(menuExample);
                for (TenantMenu tenantMenu : tenantMenus) {
                    if (StringUtils.isNotBlank(tenantMenu.getPermission())) {
                        permissionNames.add(tenantMenu.getPermission());
                    } else {
                        //如果还没有设置button的授权信息，就自动生成shiro的sys:menu:add授权格式,以在标签中使用
                        //if (permission.getMenuType().equals("2") && StringUtils.isNotBlank(permission.getHref())) {
                        //    //String path = "/tenant/menu/index";
                        //    String o = "/";
                        //    String r = ":";
                        //    String rep = RegExUtils.replaceAll(permission.getHref(), o, r).substring(1);
                        //    permissionNames.add(rep);
                        //
                        //    logger.info("手工生成 permissions,:{},:{}", permission.getHref(), rep);
                        //}
                    }
                }
            } else if (tenantUser.getUserType().equals(WebConstant.STATUS_ONE)) {//分销商
                //用户所属角色
                //roles.add(user.getJoinRole());

                //for (TenantRole role : roles) {
                //roleName.add(role.getRoleName());
                roleName.add(ShiroConstants.OPERATOR_ROLE_NAME);

                //用户所属角色的permissions
                List<TenantMenu> tenantMenus = tenantMenuService.listMenuByRole(tenantUser.getTenantRoleId());
                for (TenantMenu tenantMenu : tenantMenus) {
                    if (StringUtils.isNotBlank(tenantMenu.getPermission())) {
                        permissionNames.add(tenantMenu.getPermission());
                    }
                }
                //}
            } else if (tenantUser.getUserType().equals(WebConstant.STATUS_TWO)) {//企业帐户
                //用户所属角色
                roleName.add(ShiroConstants.ENTERPRISE_ROLE_NAME);

                //用户所属角色的permissions
                List<TenantMenu> tenantMenus = tenantMenuService.listMenuByEnterprise();
                for (TenantMenu tenantMenu : tenantMenus) {
                    if (StringUtils.isNotBlank(tenantMenu.getPermission())) {
                        permissionNames.add(tenantMenu.getPermission());
                    }
                }
            } else if (tenantUser.getUserType().equals(WebConstant.STATUS_THREE)) {
                TenantRole tenantRole = tenantRoleService.selectByPrimaryKey(tenantUser.getTenantRoleId());
                roleName.add(tenantRole.getRoleName());

                //管理员拥有所有permissions
                List<TenantMenu> tenantMenus = tenantMenuService.listMenuByRole(tenantUser.getTenantRoleId());
                for (TenantMenu tenantMenu : tenantMenus) {
                    if (StringUtils.isNotBlank(tenantMenu.getPermission())) {
                        permissionNames.add(tenantMenu.getPermission());
                    } else {
                        //如果还没有设置button的授权信息，就自动生成shiro的sys:menu:add授权格式,以在标签中使用
                        //if (permission.getMenuType().equals("2") && StringUtils.isNotBlank(permission.getHref())) {
                        //    //String path = "/tenant/menu/index";
                        //    String o = "/";
                        //    String r = ":";
                        //    String rep = RegExUtils.replaceAll(permission.getHref(), o, r).substring(1);
                        //    permissionNames.add(rep);
                        //
                        //    logger.info("手工生成 permissions,:{},:{}", permission.getHref(), rep);
                        //}
                    }
                }
            }

            //设置角色名
            info.setRoles(roleName);
            //设置权限名(注意权限是以字符串的形式保存的权限码)
            info.setStringPermissions(permissionNames);

            logger.info("doGetAuthorizationInfo授权:{}", ReflectionToStringBuilder.toString(info, ToStringStyle.MULTI_LINE_STYLE));
            Long roleId = BeanUtil.isValid(shiroUser.getTenantRoleId()) ? shiroUser.getTenantRoleId() : 0;
            redisTemplate.opsForValue().set("shiro:" + shiroUser.getTenantCode() + ":" + roleId, info, 1, TimeUnit.DAYS);

            return info;
        }

        return null;
    }

    @PostConstruct
    public void initCredentialsMatcher() {
        // 该句作用是重写shiro的密码验证，让shiro使用自定义验证
        setCredentialsMatcher(new CustomCredentialsMatcher());
    }

    /**
     * 更新用户授权信息缓存.
     */
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    /**
     * 更新用户信息缓存.
     */
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    /**
     * 清除用户授权信息缓存.
     */
    public void clearAllCachedAuthorizationInfo() {
        if (getAuthorizationCache() != null) {
            getAuthorizationCache().clear();
        }
    }

    /**
     * 清除用户信息缓存.
     */
    public void clearAllCachedAuthenticationInfo() {
        if (getAuthenticationCache() != null) {
            getAuthenticationCache().clear();
        }
    }

    /**
     * 清空所有缓存
     */
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    /**
     * 清空所有认证缓存
     */
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }

    // void clearAllCachedAuthorizationInfo2() {
    //    Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
    //    if (cache != null) {
    //        for (Object key : cache.keys()) {
    //            logger.info(key + ".............." + key.toString());
    //            AuthorizationInfo v = cache.get(key);
    //
    //            cache.remove(key);
    //        }
    //    }
    //}

}
